package redis

import (
	"github.com/garyburd/redigo/redis"
	"github.com/rs/zerolog/log"
)

// HSET 将哈希表 hash 中域 field 的值设置为 value 。
func (db *Conn) HSET(key, field string, value interface{}) (success bool) {
	_, err := db.Conn.Do("HSET", key, field, value)
	if err != nil {
		log.Error().Err(err).
			Str("key", key).
			Str("field", field).
			Interface("value", value).
			Msg("redis HSET 操作失败")
		return
	}
	success = true
	return
}

// HGET 返回哈希表中给定域的值。
func (db *Conn) HGET(key, field string) string {
	res, err := redis.String(db.Conn.Do("HGET", key, field))
	if err != nil {
		log.Error().Err(err).
			Str("key", key).
			Str("field", field).
			Msg("redis HGET 操作失败")
	}
	return res
}

// HGETALL 返回哈希表 key 中，所有的域和值,并赋值给out(out是一个结构体的指针)
// out 字段使用"redis" tag 作匹配
func (db *Conn) HGETALL(key string, out interface{}) (success bool) {
	res, err := redis.Values(db.Conn.Do("HGETALL", key))
	if err != nil {
		if err != nil {
			log.Error().Err(err).
				Str("key", key).
				Msg("redis HGETALL 操作取值失败")
		}
		return
	}
	if len(res) == 0 {
		return
	}
	err = redis.ScanStruct(res, out)
	if err != nil {
		log.Error().Err(err).
			Str("key", key).
			Msg("redis HGETALL 操作赋值失败")
		return
	}
	success = true
	return
}

// HMSET HMSET操作
// 同时将多个 field-value (域-值)对设置到哈希表 key 中
// 此命令会覆盖哈希表中已存在的域
func (db *Conn) HMSET(key string, fieldValue interface{}) (success bool) {
	args := redis.Args{key}.AddFlat(fieldValue)
	_, err := db.Conn.Do("HMSET", args...)
	if err != nil {
		log.Error().Err(err).Str("key", key).Interface("value", fieldValue).Msg("redis SET 操作失败")
		return
	}
	success = true
	return success
}

// HMGET 返回哈希表 key 中，一个或多个给定域的值。
// 如果给定的域不存在于哈希表，那么返回一个 nil 值。
// 因为不存在的 key 被当作一个空哈希表来处理，所以对一个不存在的 key 进行 HMGET 操作将返回一个只带有 nil 值的表。
func (db *Conn) HMGET(key string, fields ...string) map[string]string {
	args := redis.Args{key}
	for _, field := range fields {
		args = args.Add(field)
	}
	res, err := redis.Strings(db.Conn.Do("HMGET", args...))
	if err != nil {
		log.Error().Err(err).
			Str("key", key).
			Interface("fields", fields).
			Msg("redis HMGET 操作取值失败")
	}
	r := make(map[string]string)
	for index, field := range fields {
		r[field] = res[index]
	}
	return r
}

// HINCRBY 为哈希表 key 中的域 field 的值加上增量 increment 。
// 增量也可以为负数，相当于对给定域进行减法操作。
// 如果 key 不存在，一个新的哈希表被创建并执行 HINCRBY 命令。
// 如果域 field 不存在，那么在执行命令前，域的值被初始化为 0 。
// 对一个储存字符串值的域 field 执行 HINCRBY 命令将造成一个错误。
// 本操作的值被限制在 64 位(bit)有符号数字表示之内。
// 返回值: 执行 HINCRBY 命令之后，哈希表 key 中域 field 的值。
func (db *Conn) HINCRBY(key, field string, increment int64) int64 {
	res, err := redis.Int64(db.Conn.Do("HINCRBY", key, field, increment))
	if err != nil {
		log.Error().Err(err).Str("key", key).Msg("HINCRBY操作错误")
	}
	return res
}

// HINCRBYFLOAT 为哈希表 key 中的域 field 加上浮点数增量 increment 。
// 如果哈希表中没有域 field ，那么 HINCRBYFLOAT 会先将域 field 的值设为 0 ，然后再执行加法操作。
// 如果键 key 不存在，那么 HINCRBYFLOAT 会先创建一个哈希表，再创建域 field ，最后再执行加法操作。
// 无论加法计算所得的浮点数的实际精度有多长， INCRBYFLOAT 命令的计算结果最多只保留小数点的后十七位。
// 当以下任意一个条件发生时，返回一个错误：
//     1.域 field 的值不是字符串类型(因为 redis 中的数字和浮点数都以字符串的形式保存，所以它们都属于字符串类型）
//     2.域 field 当前的值或给定的增量 increment 不能解释(parse)为双精度浮点数
//
// 返回值: 执行加法操作之后 field 域的值。
func (db *Conn) HINCRBYFLOAT(key, field string, increment float64) float64 {
	res, err := redis.Float64(db.Conn.Do("HINCRBYFLOAT", key, field, increment))
	if err != nil {
		log.Error().Err(err).Str("key", key).Float64("increment", increment).Msg("HINCRBYFLOAT操作错误")
	}
	return res
}

// HDEL 删除哈希表 key 中的一个或多个指定域，不存在的域将被忽略。
// 返回值: 被成功移除的域的数量，不包括被忽略的域。
func (db *Conn) HDEL(key string, fields ...string) int {
	args := redis.Args{key}
	for _, field := range fields {
		args = args.Add(field)
	}
	res, err := redis.Int(db.Conn.Do("HDEL", args...))
	if err != nil {
		log.Error().Err(err).
			Str("key", key).
			Interface("fields", fields).
			Msg("redis HDEL 操作失败")
	}
	if len(fields) != res {
		log.Warn().
			Int("succesed", res).
			Int("need", len(fields)).
			Str("key", key).
			Interface("fields", fields).
			Msg("redis HDEL 操作成功数和传入要删除字段的数量不相等")
	}
	return res
}
